﻿using System;
using System.Threading;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows.Forms;

using System.IO;
using System.Reflection;

using System.Runtime.InteropServices;

using Resolution;

using GECS.MapEngine;
using GECS.Input;

namespace GECS.Core {

	/// <summary>
	/// This class is the main class for any game. You should extend this class to write a game.
	/// The games require .NET 2 or more to run. This class uses double buffering.
	/// </summary>
	/// <example>
	/// <code>
	/// public class MyGame : Game
	/// {
	/// 
	///     public override void InitResources()
	///     {
	///         // Load resources
	///     }
	/// 
	///     public override void Update(long elapsedTime)
	///     {
	///         // Update the game
	///     }
	/// 
	///     public override void Render(Graphics g)
	///     {
	///         // Render the game
	///     }
	/// 
	/// }
	/// </code></example>
    public class Game : Form {

        #region variables

        private static DisplayMode originalMode;
        bool fullscreen = false;

        private static Game game;
        private static GameState state = GameState.GAME_LOADING;

        private Size size = new Size();

        private Image backbuffer = null;

        private Graphics backGraphics = null;
        private Graphics formGraphics = null;

        #endregion

        #region constructor

        /// <summary>
        /// Constructs a new game and runs it.
        /// </summary>
        public Game() {
            game = this;
            // Form properties
            size.Width = Global.WIDTH;
            size.Height = Global.HEIGHT;
            ClientSize = size;
            Text = Global.TITLE;
            StartPosition = FormStartPosition.CenterScreen;
            FormBorderStyle = FormBorderStyle.FixedSingle;
            MaximizeBox = false;
            MinimizeBox = false;
            // Styles
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.UserPaint, false);
            SetStyle(ControlStyles.FixedWidth, true);
            SetStyle(ControlStyles.FixedHeight, true);
            // Resolution
            originalMode = GraphicsDevice.getCurrentDisplayMode();
            // Graphics
            backbuffer = new Bitmap(Global.WIDTH, Global.HEIGHT, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
            backGraphics = Graphics.FromImage(backbuffer);
            backGraphics.CompositingQuality = CompositingQuality.HighSpeed;
            backGraphics.InterpolationMode = InterpolationMode.Low;
            backGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            formGraphics = CreateGraphics();
            formGraphics.CompositingMode = CompositingMode.SourceCopy;
            formGraphics.CompositingQuality = CompositingQuality.HighSpeed;
            formGraphics.SmoothingMode = SmoothingMode.None;
            formGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
            formGraphics.TextRenderingHint = TextRenderingHint.SystemDefault;
            formGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            // Input
            new GInput(this);
            // Default Icon
            Icon = new Icon(GetLibraryResourceStream("LibRes/gecs_logo.ico"));	
            // Initialization
            Map.InitMap();
            InitResources();
            Show();
            // Start game loop
            GameLoop();
        }

        #endregion

        #region  virtualmethods

        /// <summary>
        /// Initialize the resources for the game
        /// </summary>
        public virtual void InitResources() {}

        /// <summary>
        /// Update the game. Do the game logic.
        /// </summary>
        /// <param name="elapsedTime">The time taken by the last update</param>
        public virtual void Update(long elapsedTime) {}

        /// <summary>
        /// Render the game to screen.
        /// </summary>
        /// <param name="g">The graphics context</param>
        public virtual void Render(Graphics g) {}

        #endregion

        #region classmethods

        /// <summary>
        /// Implements the game loop.
        /// </summary>
        private void GameLoop() {        
            // Initialize game loop
            long now = GetCurrentTime();
            long step_size = 1000/Global.STEPS_FOR_SECOND;
            long game_time = GetCurrentTime();
            long elapsedTime = 0;
            // UPD counter
            int updates = 0;
            long lastUPDCount = GetCurrentTime();
            int maxUpdates = Global.STEPS_FOR_SECOND;
            // FPS counter
            int frames = 0;
            long lastFPSCount = GetCurrentTime();
            int loops = 0;
            while (true) {
                loops = 0;
                if (!Created) {
                    break;
                }
                Text = Global.TITLE;
                now = GetCurrentTime();
                while (now > game_time && loops <= 2) {
                    elapsedTime = 1000/Global.STEPS_FOR_SECOND;
                    if (Global.HIDE_CURSOR) {
                        Cursor.Hide();
                    } else {
                        Cursor.Show();
                    }
                    Application.DoEvents();
                    GoFullScreen();
                    Update(elapsedTime);
                    if (state==GameState.GAME_PLAYING) {
                        Map.UpdateObjects(elapsedTime);
                    }
                    game_time += step_size;
                    // calculate update count
                    updates++;
                    if (now - lastUPDCount > 1000) {
                        lastUPDCount = now;
                        Global.ACTUAL_STEPS_FOR_SECOND = updates;
                        Global.UPDATE_RATE = (int)((float)((float)Global.ACTUAL_STEPS_FOR_SECOND/(float)Global.STEPS_FOR_SECOND)*100);
                        updates = 0;
                    }
                    loops++;
                }
                try {
                    backGraphics.Clear(Color.WhiteSmoke);
                    Render(backGraphics);
                    formGraphics.DrawImageUnscaled(backbuffer, 0, 0);
                    // FPS counter
                    frames++;
                    if (now - lastFPSCount > 1000) {
                        lastFPSCount = now;
                        Global.FRAMES_PER_SECOND = frames;
                        frames = 0;
                    }
                    Thread.Sleep(10);
                } catch {
                    /* Only arises due to creating from non existant form. Game closes automatically */
                }
            }
            EndGame();
        }

        /// <summary>
        /// Checks and changes the fullscreen state of the game.
        /// </summary>
        private void GoFullScreen() {
            if (Global.FULLSCREEN) {
                if (!fullscreen) {
                    // Get Display modes
                    DisplayMode[] modes = GraphicsDevice.getAvailableModes();
                    foreach (DisplayMode m in modes) {
                        if (m.WIDTH == Global.WIDTH && m.HEIGHT == Global.HEIGHT && m.BIT_DEPTH >= 32) {
                            GraphicsDevice.setDisplayMode(m);
                            GraphicsDevice.setFullscreen(this);
                            fullscreen = true;
                            break;
                        }
                    }
                }
            } else {
                if (fullscreen) {
                    GraphicsDevice.setDisplayMode(originalMode);
                    GraphicsDevice.leaveFullScreen(this);
                    CenterToScreen();
                    fullscreen = false;
                }
            }
        }

        #endregion

        #region utilmethods

        /// <summary>
        /// Terminate the game.
        /// </summary>
        public static void EndGame() {
            if (Global.FULLSCREEN) {
                GraphicsDevice.setDisplayMode(originalMode);
                GraphicsDevice.leaveFullScreen(game);
            }
            Application.Exit();
        }

        /// <summary>
        /// Returns the current time in milliseconds with the high resolution timer available.
        /// </summary>
        /// <returns></returns>
        public static long GetCurrentTime() {
            return System.Diagnostics.Stopwatch.GetTimestamp() / (System.Diagnostics.Stopwatch.Frequency/1000);
        }

        /// <summary>
        /// Loads image from file.
        /// </summary>
        /// <param name="name">The name of the file</param>
        /// <returns>The loaded image object. Returns null if the file can't be found.</returns>
        public static Image LoadImageFromFile(string name) {
            try {
                return Image.FromFile(GetFullFileName(name));
            } catch {
                return null;
            }
        }

        /// <summary>
        /// Loads an image no matter where it is present. The search order is from file, resource or library resource.
        /// </summary>
        /// <param name="name">The name of the image.</param>
        /// <returns>The loaded image object. Returns null if the image can't be loaded.</returns>
        public static Image LoadImage(string name) {
            Image img;
            img = LoadImageFromFile(name);
            if (img == null){
            	img = LoadImageFromResource(name);
            	if (img == null){
            		img = LoadLibraryImage(name);
            		if (img == null){
            			throw new Exception("No Image Exception");
            		}
            	}
            }
            return img;
        }

        /// <summary>
        /// Loads an image from embedded resource present in the game assembly.
        /// </summary>
        /// <param name="name">The name of the image.</param>
        /// <returns>The loaded image object. Returns null if the image can't be loaded.</returns>
        public static Image LoadImageFromResource(string name) {
            try {
                return (Image)new Bitmap(GetResourceStream(name));
            } catch {
                return null;
            }
        }

        /// <summary>
        /// Loads an image present in the library assembly.
        /// </summary>
        /// <param name="name">The name of the image.</param>
        /// <returns>The loaded image object. Returns null if the image can't be loaded.</returns>
        public static Image LoadLibraryImage(string name) {
            try {
                return (Image)new Bitmap(GetLibraryResourceStream(name));
            } catch {
                return null;
            }
        }

        /// <summary>
        /// Creates a stream from an embedded resource in the game assembly.
        /// </summary>
        /// <param name="name">The name of the resource.</param>
        /// <returns>The loaded stream.</returns>
        public static Stream GetResourceStream(string name) {
            string asmname = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location).Replace('\\', '.').Replace('/', '.');
            return Assembly.GetEntryAssembly().GetManifestResourceStream(asmname + "." + name.Replace('\\', '.').Replace('/', '.'));
        }

        /// <summary>
        /// Creates a stream from an embedded resource present in the gecs assembly.
        /// </summary>
        /// <param name="name">The name of the resource.</param>
        /// <returns>The loaded stream.</returns>
        public static Stream GetLibraryResourceStream(string name) {
            string asmname = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location).Replace('\\', '.').Replace('/', '.');
            return Assembly.GetExecutingAssembly().GetManifestResourceStream(asmname + "." + name.Replace('\\', '.').Replace('/', '.'));
        }

        /// <summary>
        /// Converts a relative file name to a full file name.
        /// </summary>
        /// <param name="name">The relative file name</param>
        /// <returns>The full path of the file</returns>
        public static string GetFullFileName(string name) {
            return (Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + name);
        }

        /// <summary>
        /// Loads an icon from an embedded resource. Use the string "null" to set the default icon.
        /// </summary>
        /// <param name="name">The icon file path.</param>
        /// <returns>Loaded Icon object</returns>
        public static Icon LoadIcon(string name) {
            if (name.Equals("null")) {
                return new Icon(GetLibraryResourceStream("LibRes/gecs_logo.ico"));
            } else {
                return new Icon(GetResourceStream(name));
            }
        }

        #endregion

        #region properties

        /// <summary>
        /// Gets or sets the GameState.
        /// </summary>
        public static GameState State {
            get {
                return state;
            } set {
                state = value;
            }
        }

        /// <summary>
        /// Gets the only instance of the game. The user plays a single game at once!
        /// </summary>
        public static Game Instance {
            get {
                return game;
            }
        }

        #endregion

    }

}